package text;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;

import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import javax.swing.text.AttributeSet;
import javax.swing.text.BoxView;
import javax.swing.text.Element;
import javax.swing.text.JTextComponent;
import javax.swing.text.Position;
import javax.swing.text.View;

/**
 * Root view which perform pagination and paints frame around pages.
 * 
 * @author Stanislav Lapitsky
 * @version 1.0
 */
class SectionView extends BoxView {
	
	private final PageableEditorKit editorKit;

	int pageNumber = 0;

	/**
	 * Creates view instace
	 * 
	 * @param elem
	 *            Element
	 * @param axis
	 *            int
	 */
	public SectionView(PageableEditorKit editorKit, Element elem, int axis) {
		super(elem, axis);
		this.editorKit = editorKit;
	}

	/**
	 * Gets amount of pages
	 * 
	 * @return int
	 */
	public int getPageCount() {
		return pageNumber;
	}

	/**
	 * Perform layout on the box
	 * 
	 * @param width
	 *            the width (inside of the insets) >= 0
	 * @param height
	 *            the height (inside of the insets) >= 0
	 */
	public void layout(int width, int height) {
		width = editorKit.pageWidth - 2 * PageableEditorKit.DRAW_PAGE_INSET - editorKit.pageMargins.left
				- editorKit.pageMargins.right;
		this.setInsets((short) (PageableEditorKit.DRAW_PAGE_INSET + editorKit.pageMargins.top),
				(short) (PageableEditorKit.DRAW_PAGE_INSET + editorKit.pageMargins.left),
				(short) (PageableEditorKit.DRAW_PAGE_INSET + editorKit.pageMargins.bottom),
				(short) (PageableEditorKit.DRAW_PAGE_INSET + editorKit.pageMargins.right));
		super.layout(width, height);
	}

	/**
	 * Determines the maximum span for this view along an axis.
	 * 
	 * overriddedn
	 */
	public float getMaximumSpan(int axis) {
		return getPreferredSpan(axis);
	}

	/**
	 * Determines the minimum span for this view along an axis.
	 * 
	 * overriddedn
	 */
	public float getMinimumSpan(int axis) {
		return getPreferredSpan(axis);
	}

	/**
	 * Determines the preferred span for this view along an axis. overriddedn
	 */
	public float getPreferredSpan(int axis) {
		float span = 0;
		if (axis == View.X_AXIS) {
			span = editorKit.pageWidth;
		} else {
			span = editorKit.pageHeight * getPageCount();
		}
		return span;
	}

	/**
	 * Performs layout along Y_AXIS with shifts for pages.
	 * 
	 * @param targetSpan
	 *            int
	 * @param axis
	 *            int
	 * @param offsets
	 *            int[]
	 * @param spans
	 *            int[]
	 */
	protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets,
			int[] spans) {
		super.layoutMajorAxis(targetSpan, axis, offsets, spans);
		// validate header and footer sizes if necessary
		editorKit.validateHF();
		addHF();

		int n = offsets.length;
		pageNumber = 0;
		int headerHeight = editorKit.header != null ? editorKit.header.getHeight() + PageableEditorKit.HF_SHIFT : 0;
		int footerHeight = editorKit.footer != null ? editorKit.footer.getHeight() + PageableEditorKit.HF_SHIFT : 0;
		int totalOffset = headerHeight;
		for (int i = 0; i < n; i++) {
			offsets[i] = totalOffset;
			View v = getView(i);
			if (isPageBreak(v)) {
				offsets[i] = pageNumber * editorKit.pageHeight + headerHeight;
				pageNumber++;
			}

			if (v instanceof MultiPageView) {
				((MultiPageView) v).setBreakSpan(0);
				((MultiPageView) v).setAdditionalSpace(0);
			}

			if ((offsets[i] + spans[i]) > (pageNumber * editorKit.pageHeight
					- PageableEditorKit.DRAW_PAGE_INSET * 2 - editorKit.pageMargins.top
					- editorKit.pageMargins.bottom - footerHeight)) {
				if ((v instanceof MultiPageView) && (v.getViewCount() > 1)) {
					MultiPageView multipageView = (MultiPageView) v;
					int space = offsets[i] - (pageNumber - 1) * editorKit.pageHeight;
					int breakSpan = (pageNumber * editorKit.pageHeight - PageableEditorKit.DRAW_PAGE_INSET
							* 2 - editorKit.pageMargins.top - editorKit.pageMargins.bottom - footerHeight)
							- offsets[i];
					multipageView.setBreakSpan(breakSpan);
					multipageView.setPageOffset(space);
					multipageView.setStartPageNumber(pageNumber);
					multipageView.setEndPageNumber(pageNumber);
					int height = (int) getHeight();

					int width = ((BoxView) v).getWidth();
					if (v instanceof PageableParagraphView) {
						PageableParagraphView parView = (PageableParagraphView) v;
						parView.layout(width, height);
					}

					pageNumber = multipageView.getEndPageNumber();
					spans[i] += multipageView.getAdditionalSpace();
				} else {
					offsets[i] = pageNumber * editorKit.pageHeight + headerHeight;
					pageNumber++;
				}
			}
			totalOffset = (int) Math.min((long) offsets[i] + (long) spans[i],
					Integer.MAX_VALUE);
		}
	}

	protected boolean isPageBreak(View v) {
		AttributeSet attrs = v.getElement().getElement(
				v.getElement().getElementCount() - 1).getAttributes();
		Boolean pb = (Boolean) attrs.getAttribute(PageableEditorKit.PAGE_BREAK_ATTR_NAME);
		if (pb == null) {
			return false;
		} else {
			return pb.booleanValue();
		}
	}

	private void addHF() {
		JTextComponent text = (JTextComponent) getContainer();
		if (text != null) {
			if (editorKit.header != null && !isAdded(text, editorKit.header)) {
				text.add(editorKit.header);
				editorKit.header.setLocation(PageableEditorKit.DRAW_PAGE_INSET + editorKit.pageMargins.left,
						PageableEditorKit.DRAW_PAGE_INSET + editorKit.pageMargins.top);
			}
			if (editorKit.footer != null && !isAdded(text, editorKit.footer)) {
				editorKit.footer.setLocation(PageableEditorKit.DRAW_PAGE_INSET + editorKit.pageMargins.left,
						editorKit.pageHeight - PageableEditorKit.DRAW_PAGE_INSET - editorKit.pageMargins.bottom
								- editorKit.footer.getHeight());
				text.add(editorKit.footer);
			}
		}
	}

	protected boolean isAdded(JComponent text, JComponent c) {
		for (int i = 0; i < text.getComponentCount(); i++) {
			if (text.getComponent(i) == c) {
				return true;
			}
		}
		return false;
	}

	public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
		Point location = getClickedHFLocation(x, y);
		if (location != null) {
			if (location.y % editorKit.pageHeight < editorKit.pageHeight / 2) {
				// header
				editorKit.header.setLocation(location);
				SwingUtilities.invokeLater(new Runnable() {
					public void run() {
						editorKit.header.requestFocus();
					}
				});
			} else {
				// footer
				editorKit.footer.setLocation(location);
				SwingUtilities.invokeLater(new Runnable() {
					public void run() {
						editorKit.footer.requestFocus();
					}
				});
			}
			return -1;
		} else {
			return super.viewToModel(x, y, a, bias);
		}
	}

	public Point getClickedHFLocation(float x, float y) {
		if (!(x >= PageableEditorKit.DRAW_PAGE_INSET + editorKit.pageMargins.left && x <= editorKit.pageWidth
				- PageableEditorKit.DRAW_PAGE_INSET - editorKit.pageMargins.right)) {
			return null;
		}
		if (editorKit.getHeader() == null || editorKit.footer == null) {
			return null;
		}
		
		int headerHeight = editorKit.getHeader().getHeight();
		int footerHeight = editorKit.getFooter().getHeight();
		int headerStartY = PageableEditorKit.DRAW_PAGE_INSET + editorKit.pageMargins.top;
		int footerStartY = editorKit.pageHeight - PageableEditorKit.DRAW_PAGE_INSET - editorKit.pageMargins.bottom
				- footerHeight;
		for (int i = 0; i < getPageCount(); i++) {
			if (y < headerStartY) {
				return null;
			}
			if (y < headerStartY + headerHeight) {
				return new Point(PageableEditorKit.DRAW_PAGE_INSET + editorKit.pageMargins.left,
						headerStartY);
			}
			if (y > footerStartY && y < footerStartY + footerHeight) {
				return new Point(PageableEditorKit.DRAW_PAGE_INSET + editorKit.pageMargins.left,
						footerStartY);
			}
			headerStartY += editorKit.pageHeight;
			footerStartY += editorKit.pageHeight;
		}
		return null;
	}

	/**
	 * Paints view content and page frames.
	 * 
	 * @param g
	 *            Graphics
	 * @param a
	 *            Shape
	 */
	public void paint(Graphics g, Shape a) {
		Rectangle alloc = (a instanceof Rectangle) ? (Rectangle) a : a
				.getBounds();
		Shape baseClip = g.getClip().getBounds();
		int pageCount = getPageCount();
		Rectangle page = new Rectangle();
		page.x = alloc.x;
		page.y = alloc.y;
		page.height = editorKit.pageHeight;
		page.width = editorKit.pageWidth;
		for (int i = 0; i < pageCount; i++) {
			page.y = alloc.y + editorKit.pageHeight * i;
			paintPageFrame(g, page, (Rectangle) baseClip);
			paintHeader(g, i, page);
			paintFooter(g, i, page);
		}
		super.paint(g, a);
		g.setColor(Color.gray);
		// Fills background of pages
		int currentWidth = (int) alloc.getWidth();
		int currentHeight = (int) alloc.getHeight();
		int w = 0;
		int h = 0;
		if (editorKit.pageWidth < currentWidth) {
			w = currentWidth;
			h = currentHeight;
			g.fillRect(page.x + page.width, alloc.y, w, h);
		}
		if (editorKit.pageHeight * pageCount < currentHeight) {
			w = currentWidth;
			h = currentHeight;
			g.fillRect(page.x, alloc.y + page.height * pageCount, w, h);
		}
	}

	protected void paintHeader(Graphics g, int pageIndex, Rectangle page) {
		Graphics2D g2d = (Graphics2D) g;
		if (editorKit.header != null) {
			AffineTransform old = g2d.getTransform();
			g2d.translate(PageableEditorKit.DRAW_PAGE_INSET + editorKit.pageMargins.left, PageableEditorKit.DRAW_PAGE_INSET
					+ editorKit.pageMargins.top + pageIndex * editorKit.pageHeight);
			boolean isCaretVisible = editorKit.header.getCaret().isVisible();
			boolean isCaretSelectionVisible = editorKit.header.getCaret()
					.isSelectionVisible();
			editorKit.header.getCaret().setVisible(false);
			editorKit.header.getCaret().setSelectionVisible(false);
			editorKit.header.paint(g2d);
			editorKit.header.getCaret().setVisible(isCaretVisible);
			editorKit.header.getCaret().setSelectionVisible(isCaretSelectionVisible);
			g2d.setColor(Color.lightGray);
			g2d.draw(new Rectangle(-1, -1, editorKit.header.getWidth() + 1, editorKit.header
					.getHeight() + 1));
			g2d.setTransform(old);
		}
	}

	protected void paintFooter(Graphics g, int pageIndex, Rectangle page) {
		Graphics2D g2d = (Graphics2D) g;
		if (editorKit.footer != null) {
			AffineTransform old = g2d.getTransform();
			g2d.translate(PageableEditorKit.DRAW_PAGE_INSET + editorKit.pageMargins.left, (pageIndex + 1)
					* editorKit.pageHeight - PageableEditorKit.DRAW_PAGE_INSET - editorKit.pageMargins.bottom
					- editorKit.footer.getHeight());
			boolean isCaretVisible = editorKit.footer.getCaret().isVisible();
			boolean isCaretSelectionVisible = editorKit.footer.getCaret()
					.isSelectionVisible();
			editorKit.footer.getCaret().setVisible(false);
			editorKit.footer.getCaret().setSelectionVisible(false);
			editorKit.footer.paint(g2d);
			editorKit.footer.getCaret().setVisible(isCaretVisible);
			editorKit.footer.getCaret().setSelectionVisible(isCaretSelectionVisible);
			g2d.setColor(Color.lightGray);
			g2d.draw(new Rectangle(-1, -1, editorKit.footer.getWidth() + 1, editorKit.footer
					.getHeight() + 1));
			g2d.setTransform(old);
		}
	}

	/**
	 * Paints frame for specified page
	 * 
	 * @param g
	 *            Graphics
	 * @param page
	 *            Shape page rectangle
	 * @param container
	 *            Rectangle
	 */
	public void paintPageFrame(Graphics g, Shape page, Rectangle container) {
		Rectangle alloc = (page instanceof Rectangle) ? (Rectangle) page : page
				.getBounds();
		
		if (container.intersection(alloc).height <= 0)
			return;
		Color oldColor = g.getColor();

		// borders
		g.setColor(Color.gray);
		g.fillRect(alloc.x, alloc.y, alloc.width, PageableEditorKit.DRAW_PAGE_INSET);
		g.fillRect(alloc.x, alloc.y, PageableEditorKit.DRAW_PAGE_INSET, alloc.height);
		g.fillRect(alloc.x, alloc.y + alloc.height - PageableEditorKit.DRAW_PAGE_INSET,
				alloc.width, PageableEditorKit.DRAW_PAGE_INSET);
		g.fillRect(alloc.x + alloc.width - PageableEditorKit.DRAW_PAGE_INSET, alloc.y,
				PageableEditorKit.DRAW_PAGE_INSET, alloc.height);

		// frame
		g.setColor(Color.black);
		g.drawLine(alloc.x + PageableEditorKit.DRAW_PAGE_INSET, alloc.y + PageableEditorKit.DRAW_PAGE_INSET,
				alloc.x + alloc.width - PageableEditorKit.DRAW_PAGE_INSET, alloc.y
						+ PageableEditorKit.DRAW_PAGE_INSET);
		g.drawLine(alloc.x + PageableEditorKit.DRAW_PAGE_INSET, alloc.y + PageableEditorKit.DRAW_PAGE_INSET,
				alloc.x + PageableEditorKit.DRAW_PAGE_INSET, alloc.y + alloc.height
						- PageableEditorKit.DRAW_PAGE_INSET);
		g.drawLine(alloc.x + PageableEditorKit.DRAW_PAGE_INSET, alloc.y + alloc.height
				- PageableEditorKit.DRAW_PAGE_INSET, alloc.x + alloc.width - PageableEditorKit.DRAW_PAGE_INSET,
				alloc.y + alloc.height - PageableEditorKit.DRAW_PAGE_INSET);
		g.drawLine(alloc.x + alloc.width - PageableEditorKit.DRAW_PAGE_INSET, alloc.y
				+ PageableEditorKit.DRAW_PAGE_INSET, alloc.x + alloc.width - PageableEditorKit.DRAW_PAGE_INSET,
				alloc.y + alloc.height - PageableEditorKit.DRAW_PAGE_INSET);

		// shadow
		g.fillRect(alloc.x + alloc.width - PageableEditorKit.DRAW_PAGE_INSET, alloc.y
				+ PageableEditorKit.DRAW_PAGE_INSET + 4, 4, alloc.height - 2 * PageableEditorKit.DRAW_PAGE_INSET);
		g.fillRect(alloc.x + PageableEditorKit.DRAW_PAGE_INSET + 4, alloc.y + alloc.height
				- PageableEditorKit.DRAW_PAGE_INSET, alloc.width - 2 * PageableEditorKit.DRAW_PAGE_INSET, 4);

		g.setColor(oldColor);
	}

}